package com.justgame.project.cloud.common.util;


import com.auth0.jwt.JWT;
import com.justgame.project.cloud.common.constant.Auth2Token;
import com.justgame.project.cloud.common.constant.AuthMessage;
import com.justgame.project.cloud.common.constant.Const;
import com.justgame.project.cloud.base.entity.BaseEntity;
import com.justgame.project.cloud.common.exception.Exc;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.UnavailableSecurityManagerException;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.InvocationTargetException;
import java.util.Objects;
import java.util.Optional;

/**
 * Shiro工具
 */
@Slf4j
@Component
public class ShiroUtils {

    private static final String SESSION_ID = "session.id";

    public static Session getSession() {
        return SecurityUtils.getSubject().getSession();
    }

    public static Subject getSubject() {
        return SecurityUtils.getSubject();
    }

    public static Object getUserEntity() {
        try {
            return SecurityUtils.getSubject().getPrincipal();
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 判断登陆人是否市Admin
     *
     * @return boolean
     */
    public static Boolean isAdmin() {
        Object user = getUserEntity();
        if (ObjUtil.checkId(user)) {
            try {
                return Boolean.valueOf(BeanUtils.getProperty(user, "isAdmin"));
            } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                return false;
            }
        }
        return false;
    }

    public static Long getUserIdElseThrow(HttpServletRequest request) {
        Object user = getUserEntity();
        try {
            if (user == null) {
                String token = request.getHeader("Authorization");
                return StringUtils.isBlank(token) ? null : Long.valueOf(JWT.decode(token).getSubject());
            }
            return Long.valueOf(BeanUtils.getProperty(user, "id"));
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static Long getUserIdElseThrow(ServerHttpRequest request) {
        Object user = getUserEntity();
        try {
            if (user == null) {
                String token = request.getHeaders().getFirst("Authorization");
                return StringUtils.isBlank(token) ? null : Long.valueOf(JWT.decode(token).getSubject());
            }
            return Long.valueOf(BeanUtils.getProperty(user, "id"));
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取当前登录用户的id，如果无法获取或者为空则抛出异常
     * @return
     */
    @SuppressWarnings("all")
    public static Long getUserIdElseThrow() {
        Long userId = null;
        try {
            Object user = getUserEntity();
            if (Objects.isNull(user)) {
                HttpServletRequest request = HttpContextUtil.getHttpRequest();
                String token = request.getHeader("Authorization");
                AssertKit.hasLength(token, AuthMessage.MSG_UN_AUTHENTICATION);
                userId = Long.valueOf(JWT.decode(token).getSubject());
            }else{
                userId = Long.valueOf(BeanUtils.getProperty(user, "id"));
            }
            return userId;
        } catch (Exception e) {
            e.printStackTrace();
            return userId;
        }finally {
            if (userId == null){
                throw new Exc(AuthMessage.MSG_UN_AUTHENTICATION,AuthMessage.CODE_UN_AUTHENTICATION);
            }
        }
    }

    /**
     * 获取当前登录用户的id，如果无法获取或者为空则抛出异常
     * @return
     */
    @SuppressWarnings("all")
    public static Long getUserId() {
        Long userId = null;
        try {
            Object user = getUserEntity();
            if (Objects.isNull(user)) {
                HttpServletRequest request = HttpContextUtil.getHttpRequest();
                String token = request.getHeader("Authorization");
                AssertKit.hasLength(token, AuthMessage.MSG_UN_AUTHENTICATION);
                userId = Long.valueOf(JWT.decode(token).getSubject());
            }else{
                userId = Long.valueOf(BeanUtils.getProperty(user, "id"));
            }
            return userId;
        } catch (Exception e) {
            return userId;
        }
    }

    public static Long getUserIdByToken(String token) {
        try {
            return StringUtils.isBlank(token) ? null : Long.valueOf(JWT.decode(token).getSubject());
        } catch (UnavailableSecurityManagerException e) {
            /* 此为未登录后调用这个方法会报错 */
            return null;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static <T extends BaseEntity> void setEntityUserId(T entity) {
        Long userId = getUserIdElseThrow();
        if (userId == null) {
            throw new Exc(Const.CODE_TOKEN_OUTDATED_CHINESE, Const.CODE_TOKEN_OUTDATED);
        }
        entity.setCreateUser(userId);
    }

    public static void setSessionAttribute(Object key, Object value) {
        getSession().setAttribute(key, value);
    }

    public static Object getSessionAttribute(Object key) {
        try {
            return getSession().getAttribute(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void login(Long id) {
        Optional.<String>ofNullable(TokenUtil.getToken(HttpContextUtil.getHttpRequest()))
                .ifPresent((token)->ShiroUtils.logout());
        String token = TokenUtil.generateToken(id);
        addToken(token,id);
        Subject subject = getSubject();
        subject.login(new Auth2Token(token));
        HttpServletResponse response = HttpContextUtil.getHttpResponse();
        response.setHeader(TokenUtil.HEADER_KEY,token);
    }

    public static void logout(){
        Subject subject = getSubject();
        delToken((String)subject.getPrincipal());
        subject.logout();
    }

    public static boolean isLogin() {
        try {
            return SecurityUtils.getSubject().getPrincipal() != null;
        } catch (UnavailableSecurityManagerException e) {
            /* 此为未登录后调用这个方法会报错 */
            return false;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public static String getKaptcha(String key) {
        Object kaptcha = getSessionAttribute(key);
        if (kaptcha == null) {
            throw new Exc("验证码已失效，请重新获取。");
        }
        getSession().removeAttribute(key);
        return kaptcha.toString();
    }

    public static String getToken() {
        try {
            return HttpContextUtil.getHttpRequest().getHeader("Authorization");
        } catch (NullPointerException e) {
            return null;
        }
    }

    public static String BuildRedisTokenKey(String Token) {
        return Const.KEY_REDIS_TOKEN_HEAD + Token;
    }

    public static String BuildRedisTokenCurrentKey(Long uid) {
        return Const.KEY_REDIS_TOKEN_CURRENT + uid;
    }

    public static String getUidByToken(String token) {
        return AppContextUtils.getBean(RedisUtils.class).get(BuildRedisTokenKey(token));
    }

    public static Long getExpireByToken(String token) {
        return AppContextUtils.getBean(RedisUtils.class).getExpire(BuildRedisTokenKey(token));
    }

    public static void delToken(String token) {
        AppContextUtils.getBean(RedisUtils.class).delete(BuildRedisTokenKey(token));
    }

    public static void delToken(String token, Long uid) {
        AppContextUtils.getBean(RedisUtils.class).delete(BuildRedisTokenKey(token));
        AppContextUtils.getBean(RedisUtils.class).delete(BuildRedisTokenCurrentKey(uid));
    }

    public static void delCurrentToken(Long uid) {
        AppContextUtils.getBean(RedisUtils.class).delete(BuildRedisTokenCurrentKey(uid));
    }

    public static void addToken(String token, Long uid) {
        AppContextUtils.getBean(RedisUtils.class).set(BuildRedisTokenKey(token), uid, Const.TIME_TIMEOUT_TOKEN);
        AppContextUtils.getBean(RedisUtils.class).set(BuildRedisTokenCurrentKey(uid), token, Const.TIME_TIMEOUT_TOKEN);
    }

    public static boolean isExpiredToken(String token, Long uid) {
        String CurrentToken = AppContextUtils.getBean(RedisUtils.class).get(BuildRedisTokenCurrentKey(uid));
        // 判断 current_token 和当前 token 是否匹配
        if (CurrentToken != null && CurrentToken.equals(token)) {
            //        获取传入的Token 是否本身已过期
            return getExpireByToken(token) <= 0;
        } else {
            return true;
        }
    }
}
